import * as Layout from './layout';
import {sendAndConfirmTransaction,sendTransaction} from './util/send-and-confirm-transaction';
import BN from 'bn.js';
import * as BufferLayout from 'buffer-layout';
import type {Connection, TransactionSignature} from '@solana/web3.js';
import {
  Account,
  PublicKey,
  Transaction,
  TransactionInstruction,
} from '@solana/web3.js';
import { WalletAdapter } from '@/wallets';
import {Buffer} from 'buffer';
import assert from 'assert';


export const TOKEN_SWAP_PROGRAM_ID: PublicKey = new PublicKey(
  '5LokBV2RCRVJFb5d4gFGehVvNCoHwYvo9u8wLkZHDrVd',
);

export class Numberu64 extends BN {
  /**
   * Convert to Buffer representation
   */
  toBuffer(): Buffer {
    const a = super.toArray().reverse();
    const b = Buffer.from(a);
    if (b.length === 8) {
      return b;
    }
    assert(b.length < 8, 'Numberu64 too large');

    const zeroPad = Buffer.alloc(8);
    b.copy(zeroPad);
    return zeroPad;
  }

  /**
   * Construct a Numberu64 from Buffer representation
   */
  // @ts-ignore
  static fromBuffer(buffer): BN {
    assert(buffer.length === 8, `Invalid buffer length: ${buffer.length}`);
    return new BN(
        [...buffer]
            .reverse()
            .map((i) => `00${i.toString(16)}`.slice(-2))
            .join(''),
        16
    );
  }
}

/**
 * A program to exchange tokens against a pool of liquidity
 */
export class TokenSwap {
  /**
   * Create a Token object attached to the specific token
   *
   * @param connection The connection to use
   * @param swapProgramId The program ID of the token-swap program
   * @param tokenProgramId The program ID of the token program
   */
  constructor(
    private connection: Connection,
    public swapProgramId: PublicKey,
    public tokenProgramId: PublicKey,
  ) {
    this.connection = connection;
    this.swapProgramId = swapProgramId;
    this.tokenProgramId = tokenProgramId;
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async createNToken(
    connection: Connection,
    payer: Account,
    programId: PublicKey,
  ){
    const transaction = new Transaction();

    const keys = [
    ] as any;
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
          {
            instruction: 6, // CreateNToken
          },
          data,
      );
      data = data.slice(0, encodeLength);
    }

    transaction.add(new TransactionInstruction({
      keys,
      programId,
      data,
    }));
    const tx_id = await sendAndConfirmTransaction(
      'createNToken',
      connection,
      transaction,
      payer,
    );
    console.log("tx_id",`https://explorer.solana.com/tx/${tx_id}`);
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async supply(
    connection: Connection,
    payer: Account,
    programId: PublicKey,
    amount: Number,
  ){
    const transaction = new Transaction();

    const keys = [
    ] as any;
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('amount'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
          {
            instruction: 7, // supply
            amount:new Numberu64(amount.toString()).toBuffer(),
          },
          data,
      );
      data = data.slice(0, encodeLength);
    }

    transaction.add(new TransactionInstruction({
      keys,
      programId,
      data,
    }));
    const tx_id = await sendAndConfirmTransaction(
      'supply',
      connection,
      transaction,
      payer,
    );

    console.log("tx_id",`https://explorer.solana.com/tx/${tx_id}`);
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async borrow(
    connection: Connection,
    payer: Account,
    programId: PublicKey,
    amount: Number,
  ){
    const transaction = new Transaction();

    const keys = [
    ] as any;
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('amount'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
          {
            instruction: 8, // borrow
            amount:new Numberu64(amount.toString()).toBuffer(),
          },
          data,
      );
      data = data.slice(0, encodeLength);
    }

    transaction.add(new TransactionInstruction({
      keys,
      programId,
      data,
    }));
    const tx_id = await sendAndConfirmTransaction(
      'borrow',
      connection,
      transaction,
      payer,
    );
    console.log("tx_id",`https://explorer.solana.com/tx/${tx_id}`);
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async payBorrow(
    connection: Connection,
    payer: Account,
    programId: PublicKey,
    amount: Number,
  ){
    const transaction = new Transaction();

    const keys = [
    ] as any;
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('amount'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
          {
            instruction: 9, // pay borrow
            amount:new Numberu64(amount.toString()).toBuffer(),
          },
          data,
      );
      data = data.slice(0, encodeLength);
    }

    transaction.add(new TransactionInstruction({
      keys,
      programId,
      data,
    }));
    const tx_id = await sendAndConfirmTransaction(
      'payBorrow',
      connection,
      transaction,
      payer,
    );
    console.log("tx_id",`https://explorer.solana.com/tx/${tx_id}`);
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async stake(
    connection: Connection,
    payer: Account,
    programId: PublicKey,
    amount: Number,
  ){
    const transaction = new Transaction();

    const keys = [
    ] as any;
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('amount'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
          {
            instruction: 10, // stake
            amount:new Numberu64(amount.toString()).toBuffer(),
          },
          data,
      );
      data = data.slice(0, encodeLength);
    }

    transaction.add(new TransactionInstruction({
      keys,
      programId,
      data,
    }));
    const tx_id = await sendAndConfirmTransaction(
      'stake',
      connection,
      transaction,
      payer,
    );
    console.log("tx_id",`https://explorer.solana.com/tx/${tx_id}`);
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async unstake(
    connection: Connection,
    payer: Account,
    programId: PublicKey,
    amount: Number,
  ){
    const transaction = new Transaction();

    const keys = [
    ] as any;
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('amount'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
          {
            instruction: 11, // unstake
            amount:new Numberu64(amount.toString()).toBuffer(),
          },
          data,
      );
      data = data.slice(0, encodeLength);
    }

    transaction.add(new TransactionInstruction({
      keys,
      programId,
      data,
    }));
    const tx_id = await sendAndConfirmTransaction(
      'unstake',
      connection,
      transaction,
      payer,
    );
    console.log("tx_id",`https://explorer.solana.com/tx/${tx_id}`);
  }




}
